package com.eelpo.framework.socket;

import java.io.IOException;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBufferInputStream;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.support.BeanDefinitionValidationException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import com.eelpo.framework.socket.listener.SocketRequestListener;
import com.eelpo.framework.socket.session.SocketSession;

import flex.messaging.io.SerializationContext;
import flex.messaging.io.amf.Amf3Input;

/**
 * 请求类处理转发器
 * 
 * @author <a href="mailto:whz-work@163.com">汪洪州</a>
 * 
 * @since 1.0 Create on May 30, 2012
 */
public class SocketRequest implements ApplicationContextAware {

	private static SocketRequest SOCKET_REQUEST;
	public static final String CONTROLLER = "Controller";
	public static final SerializationContext serializationContext = new SerializationContext();
	private Map<Integer, MethodHolder> methodHolderMap = new HashMap<Integer, MethodHolder>();

	public static SocketRequest get() {
		return SOCKET_REQUEST;
	}

	@Override
	public void setApplicationContext(ApplicationContext context) throws BeansException {
		methodHolder(context);
		SOCKET_REQUEST = this;
	}

	public void dispatch(SocketSession socketSession, ChannelBuffer channelBuffer, SocketRequestListener socketRequestListener) throws IOException, IllegalAccessException, ClassNotFoundException {

		Amf3Input amf3Input = deserialize(channelBuffer);
		int id = amf3Input.readInt();
		int action = amf3Input.readInt();

		MethodHolder holder = methodHolderMap.get(action);
		if (holder == null) {
			throw new IllegalAccessException("There the illegal action requests [action : " + action + ", ip : " + socketSession.getRemoteAddress() + "]");
		}

		if (socketSession.isLogined() || holder.action.anyone()) {
			Object[] parameters = getParameters(socketSession, holder.getMethod().getParameterTypes(), amf3Input);
			if (socketRequestListener != null) {
				socketRequestListener.beforeOnDispatch(holder, parameters);
			}

			try {
				SocketResponse response = holder.invoke(parameters);
				response.setI(id);
				response.setA(action);
				socketSession.write(response.serialize());
			} catch (Exception e) {
				if (socketRequestListener != null) {
					socketRequestListener.errorOnDispatch(holder, parameters, e);
				}
			}
		} else {
			throw new IllegalStateException("There the illegal state requests [action : " + action + ", ip : " + socketSession.getRemoteAddress() + "]");
		}
	}

	public Object[] getParameters(SocketSession socketSession, Class<?>[] classes, Amf3Input amf3Input) throws ClassNotFoundException, IOException {
		if (classes.length > 1) {
			Object[] parameters = new Object[classes.length];
			parameters[0] = socketSession;
			for (int i = 1, j = classes.length; i < j; i++) {
				parameters[i] = amf3Input.readObject();
			}
			return parameters;
		} else {
			return new Object[] { socketSession };
		}
	}

	public void methodHolder(ApplicationContext context) throws BeansException {
		String[] beans = context.getBeanDefinitionNames();
		for (String bean : beans) {
			if (bean.endsWith(CONTROLLER)) {
				Object object = context.getBean(bean);
				Method[] methods = object.getClass().getMethods();
				for (Method method : methods) {
					if (method.isAnnotationPresent(Action.class)) {
						Action action = method.getAnnotation(Action.class);
						if (!methodHolderMap.containsKey(action.value())) {
							methodHolderMap.put(action.value(), new MethodHolder(object, action, method));
						} else {
							throw new BeanDefinitionValidationException("There the same action id : " + action.value());
						}
					}
				}
			}
		}
	}

	public Amf3Input deserialize(ChannelBuffer channelBuffer) throws IOException {
		ChannelBufferInputStream channelBufferInputStream = new ChannelBufferInputStream(channelBuffer);
		Amf3Input amf3Input = new Amf3Input(serializationContext);
		amf3Input.setInputStream(channelBufferInputStream);
		return amf3Input;
	}

	public class MethodHolder {

		private Action action;
		private Object owner;
		private Method method;

		public MethodHolder(Object owner, Action action, Method method) {
			this.owner = owner;
			this.action = action;
			this.method = method;
		}

		public SocketResponse invoke(Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
			return (SocketResponse) method.invoke(owner, args);
		}

		public Method getMethod() {
			return method;
		}

		public void setMethod(Method method) {
			this.method = method;
		}

		public Object getOwner() {
			return owner;
		}

		public void setOwner(Object owner) {
			this.owner = owner;
		}

		public Action getAction() {
			return action;
		}

		public void setAction(Action action) {
			this.action = action;
		}

	}

	@Inherited
	@Documented
	@Target(value = ElementType.METHOD)
	@Retention(RetentionPolicy.RUNTIME)
	public @interface Action {
		public int value();

		public boolean anyone() default false;

		public boolean localCanmand() default false;
	}
}
